/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */


package ru.teleology.cubefs.api.reflection;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import ru.teleology.cubefs.api.reflection.number.Number;
import ru.teleology.cubefs.api.reflection.value.BooleanValue;
import ru.teleology.cubefs.api.reflection.value.ConstStringValue;
import ru.teleology.cubefs.api.reflection.value.IDValue;
import ru.teleology.cubefs.api.reflection.value.StringRefValue;
import ru.teleology.cubefs.api.reflection.value.StringValue;
import ru.teleology.cubefs.api.reflection.value.Version;

public class Types {
	public static final String TYPE_TYPE 					= "cubefs::api::Type";
	public static final String TYPED_TYPE 				= "cubefs::api::Typed";
	public static final String OPERATION_TYPE 			= "cubefs::api::Operation";
	public static final String ARGUMENT_TYPE 				= "cubefs::api::Argument";
	
	public static final String CORE_FACTORY_TYPE 			= "cubefs::api::CoreFactory";
	
	public static final String LIST_TYPE 					= "cubefs::api::List";
	public static final String TYPE_LIST_TYPE 			= "cubefs::api::TypedList<cubefs::api::Type*,-1>";
	public static final String TYPED_LIST_TYPE 			= "cubefs::api::TypedList<cubefs::api::Typed*,-1>";
	public static final String STATIC_TYPED_LIST_TYPE 	= "cubefs::api::TypedList<cubefs::api::Typed*,10>";
	
	
	
	public static final String BIGINT_LIST_TYPE 			= "cubefs::api::TypedList<BigInt,-1>";
	public static final String STRING_LIST_TYPE 			= "cubefs::api::TypedList<cubefs::api::TypedValue<String>*,-1>";
	
	public static final String BYTE_ARRAY_TYPE 			= "cubefs::api::Array<Byte>";
	
	public static final String METADATA_TYPE 				= "cubefs::api::metadata::Metadata";
	public static final String METADATA_GROUP_TYPE 		= "cubefs::api::metadata::MetadataGroup";
	public static final String MODEL_METADATA_TYPE 		= "cubefs::api::metadata::ModelMetadata";
	public static final String CONTAINER_METADATA_TYPE 	= "cubefs::api::metadata::ContainerMetadata";
	public static final String FIELD_METADATA_TYPE 		= "cubefs::api::metadata::FieldMetadata";
	public static final String PAGE_METADATA_TYPE 		= "cubefs::api::metadata::PageMetadata";
	public static final String FLAG_FIELD_TYPE 			= "cubefs::api::metadata::FlagField";
	public static final String BITMAP_FIELD_TYPE 			= "cubefs::api::metadata::BitmapField";
	public static final String NUMBER_FIELD_TYPE 			= "cubefs::api::metadata::NumberField";
	public static final String PAGE_TYPE 					= "cubefs::api::metadata::Page";
	public static final String ID_FIELD_TYPE 				= "cubefs::api::metadata::IDField";
	
	public static final String BYTE_FILED_TYPE 			= "cubefs::api::metadata::TypedField<Byte>";
	public static final String INT_FILED_TYPE 			= "cubefs::api::metadata::TypedField<Int>";
	public static final String BIGINT_FILED_TYPE 			= "cubefs::api::metadata::TypedField<BigInt>";
	public static final String LONG_FILED_TYPE 			= "cubefs::api::metadata::TypedField<Long>";
	public static final String SHORT_FILED_TYPE 			= "cubefs::api::metadata::TypedField<Short>";
	public static final String UBYTE_FILED_TYPE 			= "cubefs::api::metadata::TypedField<UShar>";
	public static final String UINT_FILED_TYPE 			= "cubefs::api::metadata::TypedField<UInt>";
	public static final String USHORT_FILED_TYPE 			= "cubefs::api::metadata::TypedField<UShort>";
	
	
	public static final String NUMBER_TYPE 				= "cubefs::api::Number";
	
	public static final String BYTE_TYPE 					= "cubefs::api::TypedNumber<Byte>";
	public static final String DOUBLE_TYPE 				= "cubefs::api::TypedNumber<double>";
	public static final String FLOAT_TYPE 				= "cubefs::api::TypedNumber<float>";
	public static final String INT_TYPE 					= "cubefs::api::TypedNumber<Int>";
	public static final String BIGINT_TYPE 				= "cubefs::api::TypedNumber<BigInt>";
	public static final String SHORT_TYPE 				= "cubefs::api::TypedNumber<Short>";
	public static final String UBYTE_TYPE 				= "cubefs::api::TypedNumber<UByte>";
	public static final String UINT_TYPE 					= "cubefs::api::TypedNumber<UInt>";
	public static final String USHORT_TYPE 				= "cubefs::api::TypedNumber<UShort>";
	
	public static final String VALUE_TYPE 				= "cubefs::api::Value";
	
	public static final String BOOL_TYPE 					= "cubefs::api::TypedValue<bool>";
	public static final String IDVALUE_TYPE 				= "cubefs::api::TypedValue<cubefs::api::IDValue>";
	public static final String VERSION_TYPE 				= "cubefs::api::TypedValue<cubefs::api::Version>";
	public static final String STRING_TYPE 				= "cubefs::api::TypedValue<String>";
	public static final String CONST_STRING_TYPE 			= "cubefs::api::TypedValue<const String>";
	public static final String STRING_REF_TYPE 			= "cubefs::api::TypedValue<StringRef>";
	public static final String CONST_VOID_PTR_TYPE 		= "cubefs::api::TypedValue<const void*>";
	public static final String VOID_PTR_TYPE 				= "cubefs::api::TypedValue<void*>";
	public static final String VOID_TYPE 					= "cubefs::api::VoidValue";
	
	
	public static final String MANAGER_TYPE 				= "cubefs::api::models::Manager";
	public static final String STREAM_MANAGER_TYPE 		= "cubefs::api::models::StreamManager";
	public static final String MODEL_TYPE 				= "cubefs::api::models::Model";
	public static final String CONTAINER_TYPE 			= "cubefs::api::models::Container";
	public static final String ITERATOR_TYPE 				= "cubefs::api::models::Iterator";
	public static final String TREE_MAP_ITERATOR_TYPE 	= "cubefs::api::models::TreeMapIterator";
	public static final String IDX_MAP_ITERATOR_TYPE 		= "cubefs::api::models::IdxMapIterator";
	public static final String COLLECTION_TYPE 			= "cubefs::api::models::Collection";
	public static final String MAP_TYPE 					= "cubefs::api::models::Map";
	public static final String MAP_PAIR_TYPE 				= "cubefs::api::models::Map::Pair";
	public static final String TREE_MAP_TYPE 				= "cubefs::api::models::TreeMap";
	public static final String IDX_MAP_TYPE 				= "cubefs::api::models::IdxMap";
	public static final String VECTOR_TYPE 				= "cubefs::api::models::Vector";
	public static final String DYN_VECTOR_TYPE 			= "cubefs::api::models::DynVector";
	public static final String DATA_ITERATOR_TYPE 		= "cubefs::api::models::DataIterator";
	public static final String DATA_TYPE 					= "cubefs::api::models::Data";
	public static final String BLOB_MAP_TYPE 				= "cubefs::api::models::BlobMap";
	public static final String BLOB_MAP_ITERATOR_TYPE 	= "cubefs::api::models::BlobMapIterator";
	public static final String CTREE_TYPE 				= "cubefs::api::models::CTree";
	public static final String CTREE_LABEL_TYPE 			= "cubefs::api::models::CTreeLabel";
	public static final String CTREE_PATH_SET_TYPE 		= "cubefs::api::models::CTreePathSet";
	
	
	
	public static final String LOGGER_TYPE 				= "cubefs::api::models::Logger";
	public static final String LOG_HANDLER_TYPE 			= "cubefs::api::models::LogHandler";
	public static final String DEFAULT_LOG_HANDLER_TYPE 	= "cubefs::api::models::DefaultLogHandler";
	public static final String JAVA_LOG_HANDLER_TYPE 		= "cubefs::api::models::JavaLogHandler";
	
	public static final String INPUT_STREAM 				= "cubefs::api::models::InputStreamHandler";
	public static final String OUTPUT_STREAM 				= "cubefs::api::models::OutputStreamHandler";
	
	private static final Class<?>[] HANDLE_PARAM = new Class[]{long.class};
	
	private static Map<Class<?>, Class<?>> primitiveTypeMap = new HashMap<Class<?>, Class<?>>();
	
	private static TypeMap[] objectTypeMap;
	
	public static Type Type;
	public static Type Typed;
	public static Type ConstString;
	public static Type String;
	public static Type StringRef;
	
	public static Type JByte;
	public static Type JShort;
	public static Type JChar;
	public static Type JInt;
	public static Type JLong;
	public static Type JFloat;
	public static Type JDouble;
	public static Type JBoolean;
	
	public static Type ID;
	
	static {
		primitiveTypeMap.put(byte.class, 		Byte.class);
		primitiveTypeMap.put(short.class, 		Short.class);
		primitiveTypeMap.put(int.class, 		Integer.class);
		primitiveTypeMap.put(long.class, 		Long.class);
		primitiveTypeMap.put(float.class, 		Float.class);
		primitiveTypeMap.put(double.class, 	Double.class);
		primitiveTypeMap.put(char.class, 		Character.class);
		primitiveTypeMap.put(boolean.class, 	Boolean.class);
	}
	
	static void init() {
		Type 	= TypeSystem.find(TYPE_TYPE);
		Typed 	= TypeSystem.find(TYPED_TYPE);

		String  = TypeSystem.find(STRING_TYPE);
		String  = TypeSystem.find(CONST_STRING_TYPE);
		StringRef = TypeSystem.find(STRING_REF_TYPE);

		JByte 	= TypeSystem.find(BYTE_TYPE);
		JShort 	= TypeSystem.find(SHORT_TYPE);
		JChar 	= TypeSystem.find(USHORT_TYPE);
		JInt	= TypeSystem.find(INT_TYPE);
		JLong	= TypeSystem.find(BIGINT_TYPE);
		JFloat	= TypeSystem.find(FLOAT_TYPE);
		JDouble	= TypeSystem.find(DOUBLE_TYPE);
		JBoolean= TypeSystem.find(BOOL_TYPE);

		ID		= TypeSystem.find(IDVALUE_TYPE);
		
		objectTypeMap = new TypeMap[]{
				new TypeMap(Byte.class, 		Types.JByte),

				new TypeMap(Short.class, 		Types.JByte),
				new TypeMap(Short.class, 		Types.JShort),

				new TypeMap(Character.class, 	Types.JByte),
				new TypeMap(Character.class, 	Types.JChar),

				new TypeMap(Integer.class, 	Types.JByte),
				new TypeMap(Integer.class, 	Types.JChar),
				new TypeMap(Integer.class, 	Types.JShort),
				new TypeMap(Integer.class, 	Types.JInt),

				new TypeMap(Long.class, 		Types.JByte),
				new TypeMap(Long.class, 		Types.JChar),
				new TypeMap(Long.class, 		Types.JShort),
				new TypeMap(Long.class, 		Types.JInt),
				new TypeMap(Long.class, 		Types.JLong),

				new TypeMap(Float.class, 		Types.JByte),
				new TypeMap(Float.class, 		Types.JChar),
				new TypeMap(Float.class, 		Types.JShort),
				new TypeMap(Float.class, 		Types.JFloat),

				new TypeMap(Double.class, 		Types.JByte),
				new TypeMap(Double.class, 		Types.JChar),
				new TypeMap(Double.class, 		Types.JShort),
				new TypeMap(Double.class, 		Types.JInt),
				new TypeMap(Double.class, 		Types.JFloat),
				new TypeMap(Double.class, 		Types.JDouble),

				new TypeMap(Boolean.class, 	Types.JBoolean),
		};
	}
	
	public static Type VoidType() {
		return TypeSystem.find(VOID_TYPE);
	}
	
	public static Type NumberType() {
		return TypeSystem.find(NUMBER_TYPE);
	}
	
	public static Type NumberFieldType() {
		return TypeSystem.find(NUMBER_FIELD_TYPE);
	}

	public static Type StringRefType() {
		return TypeSystem.find(STRING_REF_TYPE);
	}
	
	public static Type StringType() {
		return TypeSystem.find(STRING_TYPE);
	}
	
	public static Class<?> getObjectType(Class<?> clazz) {
		if (clazz.isPrimitive()) {
			return primitiveTypeMap.get(clazz);
		}
		else {
			throw new RuntimeException(clazz+" is not a Primitive");
		}
	}
	
	public static boolean isNumberAssignableFrom(Class<?> clazz0, Type type) {
		Class<?> clazz = clazz0.isPrimitive() ? Types.getObjectType(clazz0) : clazz0;
		
		for (TypeMap map: objectTypeMap) {
			if (map.clazz == clazz && map.type == type) {
				return true;
			}
		}
		
		return false;
	}
	
	public static boolean isAssignableFrom(Class<?> clazz, Type type) {
		Class<?> mapped = TypeSystem.findJavaClass(type);
		if (mapped != null) {
			if (Number.class.isAssignableFrom(mapped)) {
				return Types.isNumberAssignableFrom(clazz, type);
			}
			else if (BooleanValue.class.isAssignableFrom(mapped)) {
				return clazz == Boolean.class || clazz == boolean.class;
			}
			else if (StringRefValue.class.isAssignableFrom(mapped)) {
				return clazz == String.class;
			}
			else if (ConstStringValue.class.isAssignableFrom(mapped)) {
				return clazz == String.class;
			}
			else if (StringValue.class.isAssignableFrom(mapped)) {
				return clazz == String.class;
			}
			
			return mapped.isAssignableFrom(clazz); 
		}
		else {
			return false;
		}
	}
	
	@SuppressWarnings({ "static-access", "unchecked" })
	public static <T extends Typed> T getInstance(long handle) {
		T peer = Typed.peer(handle);
		if (peer != null) {
			return peer;
		}
		else {
			Type type = Typed.type(handle);
//			if (type.IsSameOrSubclass(Types.NumberType())) {
//				return newInstance(handle, Number.class);
//			}
//			else if (type.IsSameOrSubclass(Types.NumberFieldType())) {
//				return newInstance(handle, NumberField.class);
//			}
//			else {
				Class<T> clazz = (Class<T>)TypeSystem.getMapping().get(type.name());
				if (clazz != null) {
					return newInstance(handle, clazz);
				}
				else {
					throw new RuntimeException("Thre is not java type for native type "+type.name()+" defined in TypeSystem.");
				}
//			}
		}
	}
	
	@SuppressWarnings({ "static-access", "unchecked" })
	public static <T extends Typed> T getInstance(long handle, Class<T> clazz) {
		T peer = Typed.peer(handle);
		if (peer != null) {
			return peer;
		}
		else {
			return newInstance(handle, clazz);
		}
	}
	
	@SuppressWarnings("unchecked")
	public static <T extends Typed> T newInstance(long handle, Class<T> clazz) {
		try {
			return clazz.getConstructor(HANDLE_PARAM).newInstance(handle);
		} 
		catch (SecurityException e) {
			throw new RuntimeException(e);
		} 
		catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		} 
		catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} 
		catch (InstantiationException e) {
			throw new RuntimeException(e);
		} 
		catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} 
		catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}
	
	public static interface ArgumentTransformer {
		/**
		 * @param value is never null
		 * @return 
		 */
		Typed transform(Object value);
	}
	
	private static ArgumentTransformer TYPED_TRANSFORMER = new ArgumentTransformer() {
		public Typed transform(Object value) {
			return (Typed)value;
		}
	};
	
	private static ArgumentTransformer NUMBER_TRANSFORMER = new ArgumentTransformer() {
		public Typed transform(Object value) {
			java.lang.Number num = (java.lang.Number) value;
			return CoreFactory.GetCoreFactory().Create(num);
		}
	};
	
	private static ArgumentTransformer CHAR_TRANSFORMER = new ArgumentTransformer() {
		public Typed transform(Object value) {
			java.lang.Character num = (Character) value;
			return CoreFactory.GetCoreFactory().Create(num);
		}
	};
	
	private static ArgumentTransformer BOOLEAN_TRANSFORMER = new ArgumentTransformer() {
		public Typed transform(Object value) {
			java.lang.Boolean num = (Boolean) value;
			return CoreFactory.GetCoreFactory().Create(num);
		}
	};
	
	private static ArgumentTransformer STRING_TRANSFORMER = new ArgumentTransformer() {
		public Typed transform(Object value) {
			java.lang.String num = (String) value;
			return CoreFactory.GetCoreFactory().Create(num);
		}
	};
	
	private static ArgumentTransformer IDVALUE_TRANSFORMER = new ArgumentTransformer() {
		public Typed transform(Object value) {
			IDValue num = (IDValue) value;
			return CoreFactory.GetCoreFactory().Create(num);
		}
	};
	
	private static ArgumentTransformer VERSION_TRANSFORMER = new ArgumentTransformer() {
		public Typed transform(Object value) {
			Version num = (Version) value;
			return CoreFactory.GetCoreFactory().Create(num);
		}
	};
	
	private static final TransformMap[] argumentTransformers = {
		new TransformMap(Typed.class, 		TYPED_TRANSFORMER),
		new TransformMap(java.lang.Number.class, 	NUMBER_TRANSFORMER),
		new TransformMap(Character.class, 	CHAR_TRANSFORMER),
		new TransformMap(Boolean.class, 	BOOLEAN_TRANSFORMER),
		new TransformMap(String.class, 	STRING_TRANSFORMER),
		new TransformMap(IDValue.class, 	IDVALUE_TRANSFORMER),
		new TransformMap(Version.class, 	VERSION_TRANSFORMER),
	};
	
	public static ArgumentTransformer getTransformer(Class<?> clazz) {
		for (TransformMap map : argumentTransformers) {
			if (map.clazz.isAssignableFrom(clazz)) {
				return map.type;
			}
		}
		
		throw new RuntimeException("Can't find parameter type transformer for "+clazz);
	}
	
	private static class TypeMap {
		Class<?> clazz;
		Type type;
		TypeMap(Class<?> clazz, Type type) {
			this.clazz = clazz;
			this.type  = type;
		}
	}
	
	private static class TransformMap {
		Class<?> clazz;
		ArgumentTransformer type;
		TransformMap(Class<?> clazz, ArgumentTransformer type) {
			this.clazz = clazz;
			this.type  = type;
		}
	}
}
